| Conditions | 62 |
| Paths | > 20000 |
| Total Lines | 365 |
| Code Lines | 199 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like minimatch.js ➔ parse often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | module.exports = minimatch |
||
| 271 | function parse (pattern, isSub) { |
||
| 272 | if (pattern.length > 1024 * 64) { |
||
| 273 | throw new TypeError('pattern is too long') |
||
| 274 | } |
||
| 275 | |||
| 276 | var options = this.options |
||
| 277 | |||
| 278 | // shortcuts |
||
| 279 | if (!options.noglobstar && pattern === '**') return GLOBSTAR |
||
| 280 | if (pattern === '') return '' |
||
| 281 | |||
| 282 | var re = '' |
||
| 283 | var hasMagic = !!options.nocase |
||
| 284 | var escaping = false |
||
| 285 | // ? => one single character |
||
| 286 | var patternListStack = [] |
||
| 287 | var negativeLists = [] |
||
| 288 | var stateChar |
||
| 289 | var inClass = false |
||
| 290 | var reClassStart = -1 |
||
| 291 | var classStart = -1 |
||
| 292 | // . and .. never match anything that doesn't start with ., |
||
| 293 | // even when options.dot is set. |
||
| 294 | var patternStart = pattern.charAt(0) === '.' ? '' // anything |
||
| 295 | // not (start or / followed by . or .. followed by / or end) |
||
| 296 | : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' |
||
| 297 | : '(?!\\.)' |
||
| 298 | var self = this |
||
| 299 | |||
| 300 | function clearStateChar () { |
||
| 301 | if (stateChar) { |
||
| 302 | // we had some state-tracking character |
||
| 303 | // that wasn't consumed by this pass. |
||
| 304 | switch (stateChar) { |
||
| 305 | case '*': |
||
| 306 | re += star |
||
| 307 | hasMagic = true |
||
| 308 | break |
||
| 309 | case '?': |
||
| 310 | re += qmark |
||
| 311 | hasMagic = true |
||
| 312 | break |
||
| 313 | default: |
||
| 314 | re += '\\' + stateChar |
||
| 315 | break |
||
| 316 | } |
||
| 317 | self.debug('clearStateChar %j %j', stateChar, re) |
||
| 318 | stateChar = false |
||
| 319 | } |
||
| 320 | } |
||
| 321 | |||
| 322 | for (var i = 0, len = pattern.length, c |
||
| 323 | ; (i < len) && (c = pattern.charAt(i)) |
||
| 324 | ; i++) { |
||
| 325 | this.debug('%s\t%s %s %j', pattern, i, re, c) |
||
| 326 | |||
| 327 | // skip over any that are escaped. |
||
| 328 | if (escaping && reSpecials[c]) { |
||
| 329 | re += '\\' + c |
||
| 330 | escaping = false |
||
| 331 | continue |
||
| 332 | } |
||
| 333 | |||
| 334 | switch (c) { |
||
| 335 | case '/': |
||
| 336 | // completely not allowed, even escaped. |
||
| 337 | // Should already be path-split by now. |
||
| 338 | return false |
||
| 339 | |||
| 340 | case '\\': |
||
| 341 | clearStateChar() |
||
| 342 | escaping = true |
||
| 343 | continue |
||
| 344 | |||
| 345 | // the various stateChar values |
||
| 346 | // for the "extglob" stuff. |
||
| 347 | case '?': |
||
| 348 | case '*': |
||
| 349 | case '+': |
||
| 350 | case '@': |
||
| 351 | case '!': |
||
| 352 | this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) |
||
| 353 | |||
| 354 | // all of those are literals inside a class, except that |
||
| 355 | // the glob [!a] means [^a] in regexp |
||
| 356 | if (inClass) { |
||
| 357 | this.debug(' in class') |
||
| 358 | if (c === '!' && i === classStart + 1) c = '^' |
||
| 359 | re += c |
||
| 360 | continue |
||
| 361 | } |
||
| 362 | |||
| 363 | // if we already have a stateChar, then it means |
||
| 364 | // that there was something like ** or +? in there. |
||
| 365 | // Handle the stateChar, then proceed with this one. |
||
| 366 | self.debug('call clearStateChar %j', stateChar) |
||
| 367 | clearStateChar() |
||
| 368 | stateChar = c |
||
| 369 | // if extglob is disabled, then +(asdf|foo) isn't a thing. |
||
| 370 | // just clear the statechar *now*, rather than even diving into |
||
| 371 | // the patternList stuff. |
||
| 372 | if (options.noext) clearStateChar() |
||
| 373 | continue |
||
| 374 | |||
| 375 | case '(': |
||
| 376 | if (inClass) { |
||
| 377 | re += '(' |
||
| 378 | continue |
||
| 379 | } |
||
| 380 | |||
| 381 | if (!stateChar) { |
||
| 382 | re += '\\(' |
||
| 383 | continue |
||
| 384 | } |
||
| 385 | |||
| 386 | patternListStack.push({ |
||
| 387 | type: stateChar, |
||
| 388 | start: i - 1, |
||
| 389 | reStart: re.length, |
||
| 390 | open: plTypes[stateChar].open, |
||
| 391 | close: plTypes[stateChar].close |
||
| 392 | }) |
||
| 393 | // negation is (?:(?!js)[^/]*) |
||
| 394 | re += stateChar === '!' ? '(?:(?!(?:' : '(?:' |
||
| 395 | this.debug('plType %j %j', stateChar, re) |
||
| 396 | stateChar = false |
||
| 397 | continue |
||
| 398 | |||
| 399 | case ')': |
||
| 400 | if (inClass || !patternListStack.length) { |
||
| 401 | re += '\\)' |
||
| 402 | continue |
||
| 403 | } |
||
| 404 | |||
| 405 | clearStateChar() |
||
| 406 | hasMagic = true |
||
| 407 | var pl = patternListStack.pop() |
||
| 408 | // negation is (?:(?!js)[^/]*) |
||
| 409 | // The others are (?:<pattern>)<type> |
||
| 410 | re += pl.close |
||
| 411 | if (pl.type === '!') { |
||
| 412 | negativeLists.push(pl) |
||
| 413 | } |
||
| 414 | pl.reEnd = re.length |
||
| 415 | continue |
||
| 416 | |||
| 417 | case '|': |
||
| 418 | if (inClass || !patternListStack.length || escaping) { |
||
| 419 | re += '\\|' |
||
| 420 | escaping = false |
||
| 421 | continue |
||
| 422 | } |
||
| 423 | |||
| 424 | clearStateChar() |
||
| 425 | re += '|' |
||
| 426 | continue |
||
| 427 | |||
| 428 | // these are mostly the same in regexp and glob |
||
| 429 | case '[': |
||
| 430 | // swallow any state-tracking char before the [ |
||
| 431 | clearStateChar() |
||
| 432 | |||
| 433 | if (inClass) { |
||
| 434 | re += '\\' + c |
||
| 435 | continue |
||
| 436 | } |
||
| 437 | |||
| 438 | inClass = true |
||
| 439 | classStart = i |
||
| 440 | reClassStart = re.length |
||
| 441 | re += c |
||
| 442 | continue |
||
| 443 | |||
| 444 | case ']': |
||
| 445 | // a right bracket shall lose its special |
||
| 446 | // meaning and represent itself in |
||
| 447 | // a bracket expression if it occurs |
||
| 448 | // first in the list. -- POSIX.2 2.8.3.2 |
||
| 449 | if (i === classStart + 1 || !inClass) { |
||
| 450 | re += '\\' + c |
||
| 451 | escaping = false |
||
| 452 | continue |
||
| 453 | } |
||
| 454 | |||
| 455 | // handle the case where we left a class open. |
||
| 456 | // "[z-a]" is valid, equivalent to "\[z-a\]" |
||
| 457 | if (inClass) { |
||
| 458 | // split where the last [ was, make sure we don't have |
||
| 459 | // an invalid re. if so, re-walk the contents of the |
||
| 460 | // would-be class to re-translate any characters that |
||
| 461 | // were passed through as-is |
||
| 462 | // TODO: It would probably be faster to determine this |
||
| 463 | // without a try/catch and a new RegExp, but it's tricky |
||
| 464 | // to do safely. For now, this is safe and works. |
||
| 465 | var cs = pattern.substring(classStart + 1, i) |
||
| 466 | try { |
||
| 467 | RegExp('[' + cs + ']') |
||
| 468 | } catch (er) { |
||
| 469 | // not a valid class! |
||
| 470 | var sp = this.parse(cs, SUBPARSE) |
||
| 471 | re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' |
||
| 472 | hasMagic = hasMagic || sp[1] |
||
| 473 | inClass = false |
||
| 474 | continue |
||
| 475 | } |
||
| 476 | } |
||
| 477 | |||
| 478 | // finish up the class. |
||
| 479 | hasMagic = true |
||
| 480 | inClass = false |
||
| 481 | re += c |
||
| 482 | continue |
||
| 483 | |||
| 484 | default: |
||
| 485 | // swallow any state char that wasn't consumed |
||
| 486 | clearStateChar() |
||
| 487 | |||
| 488 | if (escaping) { |
||
| 489 | // no need |
||
| 490 | escaping = false |
||
| 491 | } else if (reSpecials[c] |
||
| 492 | && !(c === '^' && inClass)) { |
||
| 493 | re += '\\' |
||
| 494 | } |
||
| 495 | |||
| 496 | re += c |
||
| 497 | |||
| 498 | } // switch |
||
| 499 | } // for |
||
| 500 | |||
| 501 | // handle the case where we left a class open. |
||
| 502 | // "[abc" is valid, equivalent to "\[abc" |
||
| 503 | if (inClass) { |
||
| 504 | // split where the last [ was, and escape it |
||
| 505 | // this is a huge pita. We now have to re-walk |
||
| 506 | // the contents of the would-be class to re-translate |
||
| 507 | // any characters that were passed through as-is |
||
| 508 | cs = pattern.substr(classStart + 1) |
||
| 509 | sp = this.parse(cs, SUBPARSE) |
||
| 510 | re = re.substr(0, reClassStart) + '\\[' + sp[0] |
||
| 511 | hasMagic = hasMagic || sp[1] |
||
| 512 | } |
||
| 513 | |||
| 514 | // handle the case where we had a +( thing at the *end* |
||
| 515 | // of the pattern. |
||
| 516 | // each pattern list stack adds 3 chars, and we need to go through |
||
| 517 | // and escape any | chars that were passed through as-is for the regexp. |
||
| 518 | // Go through and escape them, taking care not to double-escape any |
||
| 519 | // | chars that were already escaped. |
||
| 520 | for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { |
||
| 521 | var tail = re.slice(pl.reStart + pl.open.length) |
||
| 522 | this.debug('setting tail', re, pl) |
||
| 523 | // maybe some even number of \, then maybe 1 \, followed by a | |
||
| 524 | tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { |
||
| 525 | if (!$2) { |
||
| 526 | // the | isn't already escaped, so escape it. |
||
| 527 | $2 = '\\' |
||
| 528 | } |
||
| 529 | |||
| 530 | // need to escape all those slashes *again*, without escaping the |
||
| 531 | // one that we need for escaping the | character. As it works out, |
||
| 532 | // escaping an even number of slashes can be done by simply repeating |
||
| 533 | // it exactly after itself. That's why this trick works. |
||
| 534 | // |
||
| 535 | // I am sorry that you have to see this. |
||
| 536 | return $1 + $1 + $2 + '|' |
||
| 537 | }) |
||
| 538 | |||
| 539 | this.debug('tail=%j\n %s', tail, tail, pl, re) |
||
| 540 | var t = pl.type === '*' ? star |
||
| 541 | : pl.type === '?' ? qmark |
||
| 542 | : '\\' + pl.type |
||
| 543 | |||
| 544 | hasMagic = true |
||
| 545 | re = re.slice(0, pl.reStart) + t + '\\(' + tail |
||
| 546 | } |
||
| 547 | |||
| 548 | // handle trailing things that only matter at the very end. |
||
| 549 | clearStateChar() |
||
| 550 | if (escaping) { |
||
| 551 | // trailing \\ |
||
| 552 | re += '\\\\' |
||
| 553 | } |
||
| 554 | |||
| 555 | // only need to apply the nodot start if the re starts with |
||
| 556 | // something that could conceivably capture a dot |
||
| 557 | var addPatternStart = false |
||
| 558 | switch (re.charAt(0)) { |
||
| 559 | case '.': |
||
| 560 | case '[': |
||
| 561 | case '(': addPatternStart = true |
||
| 562 | } |
||
| 563 | |||
| 564 | // Hack to work around lack of negative lookbehind in JS |
||
| 565 | // A pattern like: *.!(x).!(y|z) needs to ensure that a name |
||
| 566 | // like 'a.xyz.yz' doesn't match. So, the first negative |
||
| 567 | // lookahead, has to look ALL the way ahead, to the end of |
||
| 568 | // the pattern. |
||
| 569 | for (var n = negativeLists.length - 1; n > -1; n--) { |
||
| 570 | var nl = negativeLists[n] |
||
| 571 | |||
| 572 | var nlBefore = re.slice(0, nl.reStart) |
||
| 573 | var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) |
||
| 574 | var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) |
||
| 575 | var nlAfter = re.slice(nl.reEnd) |
||
| 576 | |||
| 577 | nlLast += nlAfter |
||
| 578 | |||
| 579 | // Handle nested stuff like *(*.js|!(*.json)), where open parens |
||
| 580 | // mean that we should *not* include the ) in the bit that is considered |
||
| 581 | // "after" the negated section. |
||
| 582 | var openParensBefore = nlBefore.split('(').length - 1 |
||
| 583 | var cleanAfter = nlAfter |
||
| 584 | for (i = 0; i < openParensBefore; i++) { |
||
| 585 | cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') |
||
| 586 | } |
||
| 587 | nlAfter = cleanAfter |
||
| 588 | |||
| 589 | var dollar = '' |
||
| 590 | if (nlAfter === '' && isSub !== SUBPARSE) { |
||
| 591 | dollar = '$' |
||
| 592 | } |
||
| 593 | var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast |
||
| 594 | re = newRe |
||
| 595 | } |
||
| 596 | |||
| 597 | // if the re is not "" at this point, then we need to make sure |
||
| 598 | // it doesn't match against an empty path part. |
||
| 599 | // Otherwise a/* will match a/, which it should not. |
||
| 600 | if (re !== '' && hasMagic) { |
||
| 601 | re = '(?=.)' + re |
||
| 602 | } |
||
| 603 | |||
| 604 | if (addPatternStart) { |
||
| 605 | re = patternStart + re |
||
| 606 | } |
||
| 607 | |||
| 608 | // parsing just a piece of a larger pattern. |
||
| 609 | if (isSub === SUBPARSE) { |
||
| 610 | return [re, hasMagic] |
||
| 611 | } |
||
| 612 | |||
| 613 | // skip the regexp for non-magical patterns |
||
| 614 | // unescape anything in it, though, so that it'll be |
||
| 615 | // an exact match against a file etc. |
||
| 616 | if (!hasMagic) { |
||
| 617 | return globUnescape(pattern) |
||
| 618 | } |
||
| 619 | |||
| 620 | var flags = options.nocase ? 'i' : '' |
||
| 621 | try { |
||
| 622 | var regExp = new RegExp('^' + re + '$', flags) |
||
| 623 | } catch (er) { |
||
| 624 | // If it was an invalid regular expression, then it can't match |
||
| 625 | // anything. This trick looks for a character after the end of |
||
| 626 | // the string, which is of course impossible, except in multi-line |
||
| 627 | // mode, but it's not a /m regex. |
||
| 628 | return new RegExp('$.') |
||
| 629 | } |
||
| 630 | |||
| 631 | regExp._glob = pattern |
||
| 632 | regExp._src = re |
||
| 633 | |||
| 634 | return regExp |
||
| 635 | } |
||
| 636 | |||
| 924 |